Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optimal Clustering of Substations for Topology Optimization Using the Louvain Algorithm #620

Open
wants to merge 14 commits into
base: dev_multiagent
Choose a base branch
from

Conversation

BamunugeDR99
Copy link

@BamunugeDR99 BamunugeDR99 commented Jun 27, 2024

Related Problem & Feature Request

Related Problem

In the official Grid2Op repo; a Multi-Agent Reinforcement Learning(MARL) example is currently being developed under the branch dev_multiagent .

In the current implementation, when creating the multi-agent environment a parameter called ACTION_DOMAINS is passed. This represents a dictionary that maps agent names to a list of substations they control. This approach aims to cluster the grid into smaller subgrids, each managed by an individual agent.

However, the primary issue with the current implementation is the lack of an optimal clustering methodology. Currently; as shown below, the agents and the substations they control are hard-coded (i.e. the substation ids are manually assigned by hand) for the l2rpn_case14_sandbox environment.

ACTION_DOMAINS = {
        'agent_0' : [0, 1, 2, 3, 4],
        'agent_1' : [5, 6, 7, 8, 9, 10, 11, 12, 13]
    }

This hardcoded clustering approach is not ideal because:

  1. Lack of Flexibility: Hardcoding limits the adaptability of the solution to different environments and scenarios.

  2. Suboptimal Clustering: Without a systematic method for clustering, the current setup may not effectively optimize the agents' performance.

Feature Request

Due to the limitations of the current hardcoded clustering, our team @rootcodelabs is exploring methods to perform this clustering more optimally. Currently, manually assigning IDs to agents and clustering the grid does not adequately consider factors such as connectivity and the inherent relationships between different sections of the grid. This can lead to sub-optimal performance of the agent. To address this, we are utilizing a scalable clustering algorithm that can better partition the grid. This algorithm will ensure optimal clustering, with subgrids sharing more internal connectivity. This approach aims to improve overall performance and scalability by creating clusters that are more logically connected and efficient.

By addressing this issue, we aim to strengthen the current MARL approach implemented within Grid2Op.

Detailed feature request is available at (#613)

Solution: Clustering Substations using the Louvain Algorithm

To improve substation clustering in the Grid2Op environment, our team explored various graph clustering algorithms. The basis for exploring these algorithms lies in their ability to analyze and partition the grid based on connectivity. Graph clustering algorithms help identify communities or clusters within a network, ensuring that closely connected substations are grouped together. This results in more cohesive subgrids and optimized performance. After evaluating multiple such algorithms, we selected the Louvain Algorithm for its superior performance in detecting community structures within large networks and its ability to maximize modularity. This ensures that the resulting clusters have dense internal connections and sparser connections between them, making it particularly suitable for our needs.

Solution Implementation

  • The solution is implemented as a utility function and can be viewed in utils.py
from grid2op.Environment import Environment
import numpy as np
from sknetwork.clustering import Louvain
from scipy.sparse import csr_matrix

class ClusterUtils:
    """
    Outputs clustered substation based on the Louvain graph clustering method.
    """
    
    # Create connectivity matrix
    @staticmethod
    def create_connectivity_matrix(env:Environment):
        """
        Creates a connectivity matrix for the given grid environment.

        The connectivity matrix is a 2D NumPy array where the element at position (i, j) is 1 if there is a direct 
        connection between substation i and substation j, and 0 otherwise. The diagonal elements are set to 1
        to indicate self-connections.

        Args:
            env (grid2op.Environment): The grid environment for which the connectivity matrix is to be created.

        Returns:
            connectivity_matrix: A 2D Numpy array of dimension (env.n_sub, env.n_sub) representing the 
            substation connectivity of the grid environment.
        """
        connectivity_matrix = np.zeros((env.n_sub, env.n_sub))
        for line_id in range(env.n_line):
            orig_sub = env.line_or_to_subid[line_id]
            extrem_sub = env.line_ex_to_subid[line_id]
            connectivity_matrix[orig_sub, extrem_sub] = 1
            connectivity_matrix[extrem_sub, orig_sub] = 1
        return connectivity_matrix + np.eye(env.n_sub)

    
       
    # Cluster substations
    @staticmethod
    def cluster_substations(env:Environment):
        """
        Clusters substations in a power grid environment using the Louvain community detection algorithm.

       This function generates a connectivity matrix representing the connections between substations in the given
       environment; and applies the Louvain algorithm to cluster the substations into communities. The resulting 
       clusters are formatted into a dictionary where each key corresponds to an agent and the value is a list of 
       substations assigned to that agent.

        Args:
            env (grid2op.Environment): The grid environment for which the connectivity matrix is to be created.
            
        Returns:
                (MADict):
                    - keys : agents' names 
                    - values : list of substations' id under the control of the agent.
        """

        # Generate the connectivity matrix
        matrix = ClusterUtils.create_connectivity_matrix(env)

        # Perform clustering using Louvain algorithm
        louvain = Louvain()
        adjacency = csr_matrix(matrix)
        labels = louvain.fit_predict(adjacency)

        # Group substations into clusters
        clusters = {}
        for node, label in enumerate(labels):
            if label not in clusters:
                clusters[label] = []
            clusters[label].append(node)

        # Format the clusters
        formatted_clusters = {f'agent_{i}': nodes for i, nodes in enumerate(clusters.values())}
        
        return formatted_clusters

Sample Execution & Output

  • Sample execution can be viewed in ray_example3.py
import grid2op
from lightsim2grid import LightSimBackend
from grid2op.multi_agent import ClusterUtils


ENV_NAME = "l2rpn_case14_sandbox"
env = grid2op.make(ENV_NAME, backend=LightSimBackend())

# Get ACTION_DOMAINS by clustering the substations
ACTION_DOMAINS = ClusterUtils.cluster_substations(env)

Output

{
'agent_0': [0, 1, 2, 3, 4],
'agent_1': [5, 11, 12], 
'agent_2': [6, 7, 8, 13], 
'agent_3': [9, 10]
}

@BDonnot
Copy link
Collaborator

BDonnot commented Sep 23, 2024

Hello,

Some tests does not appear to pass, but i'm not sure it's due to your PR.

As soon as I can i'll fix the "dev_multiagent" branch and tests should pass (hopefully) here too.

Sorry for the delay :-/

@Thirunayan22
Copy link

Hi @BDonnot ,
Please feel free to let us know if there are any specific concerns or additional steps we can take to facilitate the merge? We’re committed to aligning with the repository’s standards and would be happy to make any adjustments if needed.

Thank you!

@BDonnot
Copy link
Collaborator

BDonnot commented Nov 5, 2024

Hello,

Thanks for this reminder.

There is absolutely no problem with this PR. I just need to find enough time to synch this branch with master and make tests pass (which has nothing to do with your code :-/)

I'll try to have a look this week. Without any promises though...

@Thirunayan22
Copy link

That's great! Thank you

Copy link
Collaborator

@BDonnot BDonnot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this addition. It's really great 😊

I would personally refactor the clusterutils file and make it a module.
And within this module, you make a LouvainClustering class.
This would have the advantage that, if you (or someone else) wants to add another algorithm it can more easily this way.

Also, can you please write your name as the author of the file 😊 you are the author 😉

Lastly, can you make some unit testing of your class?

(I'll have a few other comments when this will be done)

And sorry for the delay :-/



class ClusterUtils:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you instead make a module (folder) names cluster_utils or clusterutils
And have this class named eg "LouvainClustering" to be more explicit 😊

Copy link
Author

@BamunugeDR99 BamunugeDR99 Nov 13, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi Benjamin @BDonnot !
Addressed this comment by moving the ClusterUtils in to a seperate module and renamed it to LouvainClutering

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added unit testing for the create_connectivity_matrix and cluster_substations methods and added author names in AUTHORS.txt

setup.py Outdated
@@ -30,6 +30,7 @@ def my_test_suite():
"tqdm>=4.45.0",
"networkx>=2.4",
"requests>=2.23.0",
"scikit-network>=0.32.1",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be an optional dependency if possible

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved the scikit-networks dependency to the optional section

Copy link

Quality Gate Failed Quality Gate failed

Failed conditions
54.7% Duplication on New Code (required ≤ 3%)

See analysis details on SonarQube Cloud

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants